package pers.liyan.shi.ribbon;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义负载均衡策略, 每个服务调用三次, 轮询
 *
 * @author shily
 * @date 2019/5/24 11:24
 */
public class MyLBRule extends AbstractLoadBalancerRule {
    private static Logger log = LoggerFactory.getLogger(MyLBRule.class);

    /**
     * 下一个要调用的服务计数器
     */
    private AtomicInteger nextServerCyclicCounter;
    /**
     * 当前服务调用次数计数器
     */
    private AtomicInteger currentServerCallCounter;

    /**
     * 每个服务调用次数
     */
    private static final int PER_SERVER_CALL_TIME = 3;

    public MyLBRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
        this.currentServerCallCounter = new AtomicInteger(0);
    }

    @Override
    public Server choose(Object o) {
        return choose(getLoadBalancer(), o);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (; ; ) {

            int callTime = currentServerCallCounter.get();
            int newCallTime = (callTime + 1) % PER_SERVER_CALL_TIME;

            int current = nextServerCyclicCounter.get();
            int next = current;

            if (callTime == 0) {
                next = (current + 1) % modulo;
            }

            if (currentServerCallCounter.compareAndSet(callTime, newCallTime)) {
                if (nextServerCyclicCounter.compareAndSet(current, next)) {
                    return next;
                }
            }

        }
    }

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();

            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

}
